import 'package:json_annotation/json_annotation.dart';

part 'upload_task.g.dart';

enum UploadStatus {
  @JsonValue('pending')
  pending,
  @JsonValue('uploading')
  uploading,
  @JsonValue('completed')
  completed,
  @JsonValue('failed')
  failed,
  @JsonValue('cancelled')
  cancelled,
  @JsonValue('paused')
  paused,
}

@JsonSerializable()
class UploadTask {
  final String id;
  final String fileName;
  final String filePath;
  final int fileSize;
  final String md5;
  final String? mimeType;
  final String targetPath;
  final UploadStatus status;
  final int uploadedBytes;
  final double progress;
  final String? errorMessage;
  final DateTime createdTime;
  final DateTime? startTime;
  final DateTime? completedTime;
  final String? cloudId;
  final String? uploadUrl;
  final Map<String, dynamic>? uploadParams;

  UploadTask({
    required this.id,
    required this.fileName,
    required this.filePath,
    required this.fileSize,
    required this.md5,
    this.mimeType,
    required this.targetPath,
    this.status = UploadStatus.pending,
    this.uploadedBytes = 0,
    this.progress = 0.0,
    this.errorMessage,
    required this.createdTime,
    this.startTime,
    this.completedTime,
    this.cloudId,
    this.uploadUrl,
    this.uploadParams,
  });

  factory UploadTask.fromJson(Map<String, dynamic> json) =>
      _$UploadTaskFromJson(json);

  Map<String, dynamic> toJson() => _$UploadTaskToJson(this);

  factory UploadTask.fromMap(Map<String, dynamic> map) {
    return UploadTask(
      id: map['id'] ?? '',
      fileName: map['file_name'] ?? '',
      filePath: map['file_path'] ?? '',
      fileSize: map['file_size']?.toInt() ?? 0,
      md5: map['md5'] ?? '',
      mimeType: map['mime_type'],
      targetPath: map['target_path'] ?? '',
      status: UploadStatus.values.firstWhere(
        (e) => e.toString().split('.').last == map['status'],
        orElse: () => UploadStatus.pending,
      ),
      uploadedBytes: map['uploaded_bytes']?.toInt() ?? 0,
      progress: map['progress']?.toDouble() ?? 0.0,
      errorMessage: map['error_message'],
      createdTime: DateTime.parse(map['created_time'] ?? DateTime.now().toIso8601String()),
      startTime: map['start_time'] != null ? DateTime.parse(map['start_time']) : null,
      completedTime: map['completed_time'] != null ? DateTime.parse(map['completed_time']) : null,
      cloudId: map['cloud_id'],
      uploadUrl: map['upload_url'],
      uploadParams: map['upload_params'] != null
          ? Map<String, dynamic>.from(map['upload_params'])
          : null,
    );
  }

  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'file_name': fileName,
      'file_path': filePath,
      'file_size': fileSize,
      'md5': md5,
      'mime_type': mimeType,
      'target_path': targetPath,
      'status': status.toString().split('.').last,
      'uploaded_bytes': uploadedBytes,
      'progress': progress,
      'error_message': errorMessage,
      'created_time': createdTime.toIso8601String(),
      'start_time': startTime?.toIso8601String(),
      'completed_time': completedTime?.toIso8601String(),
      'cloud_id': cloudId,
      'upload_url': uploadUrl,
      'upload_params': uploadParams,
    };
  }

  UploadTask copyWith({
    String? id,
    String? fileName,
    String? filePath,
    int? fileSize,
    String? md5,
    String? mimeType,
    String? targetPath,
    UploadStatus? status,
    int? uploadedBytes,
    double? progress,
    String? errorMessage,
    DateTime? createdTime,
    DateTime? startTime,
    DateTime? completedTime,
    String? cloudId,
    String? uploadUrl,
    Map<String, dynamic>? uploadParams,
  }) {
    return UploadTask(
      id: id ?? this.id,
      fileName: fileName ?? this.fileName,
      filePath: filePath ?? this.filePath,
      fileSize: fileSize ?? this.fileSize,
      md5: md5 ?? this.md5,
      mimeType: mimeType ?? this.mimeType,
      targetPath: targetPath ?? this.targetPath,
      status: status ?? this.status,
      uploadedBytes: uploadedBytes ?? this.uploadedBytes,
      progress: progress ?? this.progress,
      errorMessage: errorMessage ?? this.errorMessage,
      createdTime: createdTime ?? this.createdTime,
      startTime: startTime ?? this.startTime,
      completedTime: completedTime ?? this.completedTime,
      cloudId: cloudId ?? this.cloudId,
      uploadUrl: uploadUrl ?? this.uploadUrl,
      uploadParams: uploadParams ?? this.uploadParams,
    );
  }

  bool get isCompleted => status == UploadStatus.completed;
  bool get isFailed => status == UploadStatus.failed;
  bool get isUploading => status == UploadStatus.uploading;
  bool get isPending => status == UploadStatus.pending;
  bool get isCancelled => status == UploadStatus.cancelled;
  bool get isPaused => status == UploadStatus.paused;

  Duration? get uploadDuration {
    if (startTime == null) return null;
    final endTime = completedTime ?? DateTime.now();
    return endTime.difference(startTime!);
  }

  double get uploadSpeed {
    final duration = uploadDuration;
    if (duration == null || duration.inSeconds == 0) return 0.0;
    return uploadedBytes / duration.inSeconds; // bytes per second
  }

  @override
  String toString() {
    return 'UploadTask(id: $id, fileName: $fileName, filePath: $filePath, fileSize: $fileSize, md5: $md5, mimeType: $mimeType, targetPath: $targetPath, status: $status, uploadedBytes: $uploadedBytes, progress: $progress, errorMessage: $errorMessage, createdTime: $createdTime, startTime: $startTime, completedTime: $completedTime, cloudId: $cloudId, uploadUrl: $uploadUrl, uploadParams: $uploadParams)';
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is UploadTask && other.id == id;
  }

  @override
  int get hashCode => id.hashCode;
}